<?php

namespace SunAdmin\Common;

use think\facade\Cache;
use Exception;

class RedisLock
{
    /**
     * 尝试获取锁，获取失败立即返回
     * @param string $key
     * @param int $expire 毫秒
     * @param string|null $value 锁的唯一标识
     * @return string|null 返回锁值，获取失败返回null
     */
    public static function tryLock($key, $expire = 10000, $value = null)
    {
        if (empty($key)) {
            throw new Exception("redis key不能为空");
        }
        $redis = Cache::store('redis')->handler();
        $value = $value ?: uniqid('', true) . mt_rand(1000, 9999);
        $result = $redis->set($key, $value, ['nx', 'px' => $expire]);
        return $result ? $value : null;
    }

    /**
     * 阻塞获取锁（可设置最大等待时间），获取失败后阻塞等待重试
     * @param string $key
     * @param int $expire 毫秒
     * @param int $waitTimeout 最大等待时间（毫秒）
     * @throws Exception
     */
    public static function lock($key, $expire = 10000, $waitTimeout = 10000)
    {
        $start = microtime(true);
        do {
            $value = self::tryLock($key, $expire);
            if ($value) {
                // 返回一个闭包，调用即可安全释放锁
                return function () use ($key, $value) {
                    RedisLock::unlock($key, $value);
                };
            }
            usleep(200 * 1000); // 200ms
        } while ((microtime(true) - $start) * 1000 < $waitTimeout);

        throw new Exception("操作太频繁了", -9999);
    }

    /**
     * 释放锁（只有持有锁的value才能释放）
     * @param string $key
     * @param string $value
     * @return bool
     */
    public static function unlock($key, $value)
    {
        $redis = Cache::store('redis')->handler();
        // 用lua脚本保证原子性
        $script = '
            if redis.call("get", KEYS[1]) == ARGV[1] then
                return redis.call("del", KEYS[1])
            else
                return 0
            end
        ';
        return $redis->eval($script, [$key, $value], 1) === 1;
    }
} 